<!--- 
	File: User.cfc
	Date Created: 11-11-2008 (Happy Veteren's Day)
	Author: Jason Dean (http://www.12robots.com)
	Based on the ESAPI Project by Jeff Williams at <a href="http://www.aspectsecurity.com">Aspect Security</a>
--->
<!--- 
	Description: The User object represents an application user or user account. There is quite a lot of information that an
	application must store for each user in order to enforce security properly. There are also many rules that govern
	authentication and identity management.

	A user account can be in one of several states. When first created, a User should be disabled, not expired, and
	unlocked. To start using the account, an administrator should enable the account. The account can be locked for a
	number of reasons, most commonly because they have failed login for too many times. Finally, the account can expire
	after the expiration date has been reached. The User must be enabled, not expired, and unlocked in order to pass
	authentication.
 ---> 

<cfcomponent output="false">
	<cffunction name="init" access="public" returntype="User" output="false">
		<cfargument name="ESAPI" required="true" />
		
		<cfset variables.ESAPI = arguments.ESAPI />		
		<cfset setLogger(variables.ESAPI.getLogger("User")) />
		
		<!--- Set a temporary userID for the anonymous user --->
		<cfset variables.userid = createUUID() />
		<cfset variables.username = "anonymous" />
		<cfset variables.screenName = "anonymous" />
		<cfset variables.CSRFToken = "" />
		<cfset variables.roles = StructNew() />
		<cfset variables.locked = false />
		<cfset variables.loggedin = false />
		<cfset variables.enabled = false />
		<cfset variables.lastHostAddress = "" />
		<cfset variables.lastPasswordChange = "" />
		<cfset variables.lastLoginTime = "" />
		<cfset variables.lastFailedLogin = "" />
		<cfset variables.expiration = "" />
		<cfset variables.failedLoginCount = 0 />
		<cfset variables.maxRoleLength = 250 />
		
		
		<cfreturn this />
	</cffunction>
	
	<cffunction name="addRole" access="public" returntype="void" output="false" hint="Will add a  role to a user">		
		<cfargument name="roleName" type="string" required="true" hint="Pass in the name of a role to add" />
		
		<cfset roleName = LCase(arguments.roleName) />
		
		<!--- TODO: Insert validator logic here --->
		<cfif true>
			<cfset StructInsert(variables.roles, roleName, rolename, true) />
		
			<cfset logger.info("Role Added: " & rolename & " User: " & getUsername()) />
		
		<cfelse>
			<cfset createObject('component', 'cfesapi.org.owasp.esapi.errors.AuthenticationException').init("Invalid Role Type")>
		</cfif>
		
	</cffunction>
	
	<cffunction name="addRoles" access="public" returntype="void" output="false" hint="Will add a Set of roles to a user">		
		<cfargument name="roles" type="any" required="true" hint="Pass in the names of roles to add as a struct or array" />
		
		<cfset var rolesInput = arguments.roles />
		
		<cfif IsArray(rolesInput)>
			<cfloop from="1" to="#ArrayLen(roles)#" index="roleIndex">
				<cfset addRole(roles[roleIndex]) />
			</cfloop>
		<cfelseif IsStruct(rolesInput)>
			<cfloop collection="#rolesInput#" item="roleItem">
				<cfset addRole(roleItem) />
			</cfloop>
		<cfelse>
			<cfset createObject('component', 'cfesapi.org.owasp.esapi.errors.AuthenticationException').init("Invalid Role Set Type")>
		</cfif>
	</cffunction>
	
	<cffunction name="changePassword" access="public" returntype="void" output="false" hint="Changes the users password. Pass in the old password and the new password">
		<cfargument name="oldPassword" type="string" required="true" hint="Pass in the old password unhashed" />
		<cfargument name="newPassword" type="string" required="true" hint="Pass in the new password" />
		<cfargument name="newPassword2" type="string" required="false" default="" hint="You are encouraged to build in logic to have the user retype the new password">	
	
		<cfset variables.ESAPI.authenticator.changePassword(this, arguments.oldPassword, arguments.newPassword, arguments.newPassword2) />
	</cffunction>
	
	<cffunction name="disable" access="public" returntype="void" output="false">
		<cfset variables.enabled = false />
		<cfset variables.logger.info("User Disabled - Username : " & getUsername()) />
	</cffunction>
	
	<cffunction name="enable" access="public" returntype="void" output="false">
		<cfset variables.enabled = true />
		<cfset variables.logger.info("User Enabled - Username : " & getUsername()) />
	</cffunction>
	
	<cffunction name="getUserId" access="public" returntype="string" output="false">
		<cfreturn variables.userid />
	</cffunction>
	
	<cffunction name="getUsername" access="public" returntype="string" output="false">
		<cfreturn variables.username />
	</cffunction>
	
	<cffunction name="getCSRFToken" access="public" returntype="String" output="false">
		<cfreturn variables.CSRFToken>
	</cffunction>
	
	<cffunction name="getExpiration" access="public" returntype="date" output="false">
		<cfreturn variables.expiration />
	</cffunction>
	
	<cffunction name="getFailedLoginCount" access="public" returntype="numeric" output="false">
		<cfreturn variables.failedLoginCount />
	</cffunction>
	
	<cffunction name="getLastFailedLogin" access="public" returntype="date" output="false">
		<cfreturn variables.lastFailedLogin />
	</cffunction>
	
	<cffunction name="getLastHostAddress" access="public" returntype="string" output="false">
		<cfreturn variables.lastHostAddress /> 
	</cffunction>
	
	<cffunction name="getLastLoginTime" access="public" returntype="date" output="false">
		<cfreturn variables.lastLoginTime />
	</cffunction>
	
	<cffunction name="getLastPasswordChange" access="public" returntype="date" output="false">
		<cfreturn variables.lastPasswordChange />
	</cffunction>
	
	<cffunction name="getRoles" access="public" returntype="Struct" output="false">
		<cfreturn variables.roles>
	</cffunction>
	
	<cffunction name="getScreenName" access="public" returntype="string" output="false">
		<cfreturn variables.screenName />
	</cffunction>
	
	<cffunction name="incrementFailedLoginCount" access="public" returntype="void" output="false">
		<cfset failedLoginCount = failedLoginCount + 1 />
	</cffunction>

	<cffunction name="isAnonymous" access="public" returntype="boolean" output="false">
		<cfif isLoggedIn()>
			<cfreturn false />
		<cfelse>
			<cfreturn true />
		</cfif>
	</cffunction>
	
	<cffunction name="isEnabled" access="public" returntype="boolean" output="false">
		<cfreturn variables.enabled />
	</cffunction>
	
	<cffunction name="isExpired" access="public" returntype="boolean" output="false">
		<cfif NOT isAnonymous() AND isDate(variables.expiration)>
			<cfif DateDiff('s', variables.expiration, Now()) GT 0>
				<cfreturn true />
			<cfelse>
				<cfreturn false />
			</cfif>
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
	<cffunction name="isInRole" access="public" returntype="boolean" output="false">
		<cfargument name="roleName" type="String" required="true" hint="Name of Role to be checked" />
		
		<cfset roleName = LCase(arguments.roleName) />
		
		<cfreturn StructKeyExists(variables.roles,rolename)>
	</cffunction>
	
	<cffunction name="isLocked" access="public" returntype="boolean" output="false">
		<cfreturn variables.locked>
	</cffunction>
	
	<cffunction name="isLoggedIn" access="public" returntype="boolean" output="false">
		<cfif variables.loggedIn>
			<cfreturn true />
		<cfelse>
			<cfreturn false />
		</cfif>
	</cffunction>
	
	<cffunction name="lock" access="public" returntype="void" output="false">		
		<cfset variables.locked = true />
	</cffunction>
	
	<cffunction name="loginWithPassword" access="public" returntype="" output="false">
	</cffunction>
	
	<cffunction name="logout" access="public" returntype="" output="false">
	</cffunction>
	
	<cffunction name="removeRole" access="public" returntype="void" output="false">
		<cfargument name="rolename" type="string" required="true" hint="Role to remove" />
		
		<cfset StructDelete(variables.roles, lcase(rolename)) />
	</cffunction>
	
	<cffunction name="resetCSRFToken" access="public" returntype="Any" output="false">
		<cfset var token = ESAPI.randomizer().getRandomString() />
		<cfset variables.CSRFToken = token />
		<cfreturn token />
	</cffunction>
	
	<cffunction name="setUserID" access="public" returntype="void" output="false">
		<cfargument name="id" type="string" required="true" hint="User ID to be set" />
		
		<cfset variables.userid = arguments.id />
	</cffunction>
	
	<cffunction name="setUsername" access="public" returntype="void" output="false">
		<cfargument name="username" type="String" required="true" hint="Username to be set" />
		
		<cfset variables.username = arguments.username />
	</cffunction>
	
	<cffunction name="setExpiration" access="public" returntype="void" output="false">
		<cfargument name="expDate" type="Date" required="true" hint="Account Expiration Date to be set" />
		
		<cfset variables.expiration = arguments.expDate>
	</cffunction>
	
	<cffunction name="setScreenName" access="public" returntype="void" output="false">
		<cfargument name="name" type="String" required="true" hint="Screenname" />
		
		<cfset variables.screenName = arguments.name />
	</cffunction>
	
	<cffunction name="setLoggedIn" access="public" returntype="void" output="false">
		<cfargument name="loggedIn" type="boolean" required="true" hint="Boolean, is the user logged in?">
		
		<cfset variables.loggedin = arguments.loggedin />
	</cffunction>
	
	<cffunction name="unlock" access="public" returntype="void" output="false">
		<cfset variables.locked = false />
	</cffunction>
	
	<cffunction name="verifyPassword" access="public" returntype="" output="false">
	</cffunction>
	
	<cffunction name="setLastFailedLogin" access="public" returntype="void" output="false">
		<cfargument name="datetime" type="date" required="true" hint="DateTime: Last Failed login" />
		
		<cfset variables.lastFailedLogin = arguments.datetime>
	</cffunction>
	
	<cffunction name="setLastHostAddress" access="public" returntype="void" output="false">
		<cfargument name="remoteHost" type="String" required="true" hint="IP address of last host address" />
		
		<cfif Len(variables.lastHostAddress) and variables.lastHostAddress NEQ arguments.remoteHost>
			<cfset createObject('component','cfesapi.org.owasp.esapi.errors.AuthenticationException').init("Remote host changed from" & variables.lastHostAddress & " to " & arguments.remoteHost) />
		</cfif>
		
		<cfset variables.lastHostAddress = arguments.remoteHost />
	</cffunction>
	
	<cffunction name="setLastLoginTime" access="public" returntype="void" output="false">
		<cfargument name="datetime" type="Date" required="false" default="#Now()#" hint="Pass zero arguments to set to Now()" />
		
		<cfset variables.lastLoginTime = arguments.datetime />
	</cffunction>
	
	<cffunction name="setLastPasswordChange" access="public" returntype="void" output="false">
		<cfargument name="datetime" type="Date" required="false" default="#Now()#" hint="Pass zero arguments to set to Now()" />
		
		<cfset variables.lastPasswordChange = arguments.datetime />
	</cffunction>

	<cffunction name="setLogger" access="public" returntype="void" output="false">
		<cfargument name="logger" type="any" required="true" hint="Logger to be used by this object" />
		
		<cfset variables.logger = arguments.logger />
	</cffunction>
</cfcomponent>